﻿// Inner Fire 游戏引擎库
// Model - 用于载入模型
//
// Copyright (c) 2025 by 尔西大帝. All rights reserved.
//
// 所属层级：应用层(4)
// 用于模型载入。
//
// Author: 尔西大帝
// Email: 2670613871@qq.com
// Created: 2025-01-17

#include "model.h"
#include <dx/mesh/geometry_generator.h>
#include <enum_parser.h>
#include <game/animation/avatar.h>
#include <game/config/config_manager.h>
#include <game/resource/binary_model.h>
#include <graphics/imath.h>
#include <kits.h>
#include <klog.h>
#include <stop_watch.h>

namespace ifire::game {

Model::Model(Assets* manager, const XmlNode& node)
    : Resource(manager, node("Name"))
    , filename_(node("Path"))
    , file_type_(ReadFileType(node("FileType")))
    , materials_(node)
    , root_node_(node("RootNode"))
    , convert_units_(node("ConvertUnits"))
    , is_simplify_node_(node("SimplifyNode", true))
    , is_normalize_weights_(node("IsNormalizeWeights", false))
    , avatar_(node("Avatar"))
    , is_cache_(node("IsCache")) {
  // 如果属于Shape，则从配置中载入
  if (file_type_ == ModelFileType::Shape) {
    node.Foreach("Shape", [this](ikit::XmlNode& node, int index) {
      shapes.emplace_back(node, index);
    });
  }
}

Model::~Model() {}

dx::DxMesh* Model::GetMesh() const {
  if (file_type_ == ModelFileType::SkinMesh) {
    return svlist_.Mesh();
  } else {
    return vlist_.Mesh();
  }
}

size_t Model::GetBoneCount() const { return GetArmature()->GetBoneCount(); }

void Model::Setup() { ConfigManager::Setup(avatar_); }

void Model::Load(ID3D12Device* device, ID3D12GraphicsCommandList* cmd_list) {
  // 根据类型决定不同的载入
  if (file_type_ == ModelFileType::Model) {
    LoadModel(device, cmd_list);
  } else if (file_type_ == ModelFileType::Shape) {
    LoadShape(device, cmd_list);
  } else if (file_type_ == ModelFileType::SkinMesh) {
    // 得到一个
    auto binary_model = std::make_unique<BinaryModel>(this);
    if (binary_model->IsCached()) {
      binary_model->Load();
    } else {
      LoadAssimp(device, cmd_list);
      binary_model->Save();
    }

    // 装配信息
    SetupLoadInfo(device, cmd_list);
  }
}

ModelFileType Model::ReadFileType(ikit::sstr name) {
  static std::pair<ikit::sstr, ModelFileType> enums[] = {
      {"Model", ModelFileType::Model}, //
      {"Shape", ModelFileType::Shape}, //
      {"SkinMesh", ModelFileType::SkinMesh},
  };
  static ikit::EnumParser<ModelFileType> modelFileTypeParser(
      enums, (int)ModelFileType::Count);
  return modelFileTypeParser.Get(name, ModelFileType::Model);
}

void Model::LoadModel(
    ID3D12Device* device, ID3D12GraphicsCommandList* cmd_list) {
  // 载入器
  Assimp::Importer importer;

  auto flag = aiProcess_Triangulate | aiProcess_GenSmoothNormals |
              aiProcess_CalcTangentSpace | aiProcess_ConvertToLeftHanded;
  scene_ = importer.ReadFile(filename_.c_str(), flag);

  // 载入
  if (!scene_ || scene_->mFlags & AI_SCENE_FLAGS_INCOMPLETE ||
      !scene_->mRootNode) {
    KERROR("ERROR::ASSIMP:: %s", importer.GetErrorString());
    return;
  }

  vlist_.Init(scene_, name);

  for (unsigned int i = 0; i < scene_->mNumMeshes; i++) {
    aiMesh* mesh = scene_->mMeshes[i];
    vlist_.ReadMesh(mesh, convert_units_);
  }

  // 计算包围盒
  // vlist_.CalculateBoundingBox();

  // 创建Mesh的资源
  vlist_.CreateResource(device, cmd_list);
}

void Model::LoadShape(
    ID3D12Device* device, ID3D12GraphicsCommandList* cmd_list) {
  // 初始化
  vlist_.Init("");

  // Shape不需要文件，直接使用项目载入

  // 生成每一个项目
  dx::GeometryGenerator geoGen;
  eastl::vector<dx::GeometryGenerator::MeshDataPtr> data(shapes.size());

  UINT current_vertex_ = 0;
  UINT current_index_ = 0;

  for (int i = 0; i < shapes.size(); i++) {
    auto item = shapes[i].Create(geoGen);
    // 设置SubMesh
    dx::DxSubmesh sub_mesh = GetMesh()->AddSubmesh(shapes[i].name, 0,
        current_index_, static_cast<UINT>(item->Indices32.size()),
        current_vertex_, static_cast<UINT>(item->Vertices.size()));

    // 累加
    current_vertex_ += static_cast<UINT>(item->Vertices.size());
    current_index_ += static_cast<UINT>(item->Indices32.size());

    // 设置进入Data
    data[i] = std::move(item);
  }

  // 用于快速设置
  auto set_vertex = [](Vertex& a, const dx::GeometryGenerator::Vertex& b) {
    a.pos = b.Position;
    a.normal = b.Normal;
    a.tangent = b.TangentU;
    a.texcoord = b.TexC;
  };

  // 到这个时候，current_vertex和index已经是所有项目的总合
  vlist_.Vertices().reserve(current_vertex_);
  vlist_.Indices().reserve(current_index_);

  // 遍历项目并且进行顶点/索引的复制
  for (UINT i = 0; i < data.size(); i++) {
    for (UINT j = 0; j < data[i]->Vertices.size(); j++) {
      set_vertex(vlist_.AddVertex(), data[i]->Vertices[j]);
    }
  }
  for (UINT i = 0; i < data.size(); i++) {
    for (UINT j = 0; j < data[i]->Indices32.size(); j++) {
      vlist_.AddIndex(data[i]->Indices32[j]);
    }
  }

  // 创建Mesh的资源
  vlist_.CalculateBoundingBox();
  vlist_.CreateResource(device, cmd_list);
}

void Model::LoadAssimp(
    ID3D12Device* device, ID3D12GraphicsCommandList* cmd_list) {
  Assimp::Importer importer;

  // aiProcess_OptimizeGraph |      // 合并重复节点（巨坑）
  auto flag = aiProcess_Triangulate | // 三角化所有网格
              aiProcess_CalcTangentSpace |
              aiProcess_LimitBoneWeights |   // 将骨骼权重减少为4个
              aiProcess_ConvertToLeftHanded; // 转化为DirectX的左手坐标系

  auto scene = importer.ReadFile(filename_.c_str(), flag);
  // 载入
  if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE ||
      !scene->mRootNode) {
    KERROR("ERROR::ASSIMP:: %s", importer.GetErrorString());
    return;
  }

  // 读取骨架
  armature_ =
      std::make_unique<Armature>(scene, is_simplify_node_, convert_units_);

  // 初始化项目
  svlist_.Init(scene, name);

  // 载入每一个Mesh
  for (UINT mesh_idx = 0; mesh_idx < scene->mNumMeshes; mesh_idx++) {
    // 读取Mesh的顶点信息
    auto& mesh = scene->mMeshes[mesh_idx];
    svlist_.ReadMesh(mesh, convert_units_);

    // 读取此Mesh的骨架
    for (UINT bone_idx = 0; bone_idx < mesh->mNumBones; bone_idx++) {
      aiBone* bone = mesh->mBones[bone_idx];
      // 得到这个Bone项目的ID
      ikit::sstr name = bone->mName.C_Str();
      auto bone_id = armature_->GetBoneId(name);
      // 如果没有id，则表示骨骼不存在，需要创建一个
      if (bone_id == -1) {
        XMFLOAT4X4 offset_mat;
        if (convert_units_) {
          offset_mat = imath::ConvertUnitsMatrix(bone->mOffsetMatrix);
        } else {
          offset_mat = imath::ConvertMatrix(bone->mOffsetMatrix);
        }
        auto& bone_info = armature_->AddBone(name, offset_mat);
        bone_id = bone_info.id;
      }

      // 写入骨骼的权重
      for (UINT weight_idx = 0; weight_idx < bone->mNumWeights; weight_idx++) {
        auto& weight = bone->mWeights[weight_idx];
        // 需要计算此Weight影响的项目，通过vlist的方法直接获得
        auto& vertex = svlist_.GetVertex(mesh_idx, weight.mVertexId);
        vertex.AddBoneData(static_cast<uint16_t>(bone_id), weight.mWeight);
      }
    }
  }

  // 归一化顶点权重
  if (is_normalize_weights_) {
    svlist_.NormalizeWeights();
  }
}

void Model::SetupLoadInfo(
    ID3D12Device* device, ID3D12GraphicsCommandList* cmd_list) {
  // 设置开始骨骼
  armature_->SetRootMotionNode(root_node_);

  // 在所有的骨骼都已经装配完毕之后，需要将节点和骨骼进行连接
  armature_->AttachBonesToNodes();

  // 设置AvatarID。在载入Model的时候，Avatar肯定已经载入完成，
  if (avatar_) {
    armature_->SetByAvatar(avatar_.get());
  }

  // 计算包围盒子
  // svlist_.CalculateBoundingBox();
  // 创建渲染项目
  svlist_.CreateResource(device, cmd_list);
}

} // namespace ifire::game